#include "opencl_helper.h"

namespace mslite {

void loadOpenCL(void) {
    LoadOpenCL();
}

void printfCallback(const char *buffer, size_t length, size_t final, void *user_data) {
    fwrite(buffer, 1, length, stdout);
}

int printCLInfo(void) {
    cl_int status = 0;
    cl_uint numPlatforms;               // 获取 Platform 个数
    status = clGetPlatformIDs(0, NULL, &numPlatforms);
    printf("\nnumPlatforms: %d\n", numPlatforms);

    cl_platform_id *platforms = (cl_platform_id *) malloc(numPlatforms * sizeof(cl_platform_id));   // 获取 Platform id
    status = clGetPlatformIDs(numPlatforms, platforms, NULL);

    for (unsigned int i = 0; i < numPlatforms; ++i) {     // 遍历所有 platform
        printf("Platform %d info:\n", i);
        size_t size;
        char str[10000];
        status = clGetPlatformInfo(platforms[i], CL_PLATFORM_NAME, 0, NULL, &size);
        status = clGetPlatformInfo(platforms[i], CL_PLATFORM_NAME, size, str, NULL);
        printf("CL_PLATFORM_NAME: %s\n", str);

        status = clGetPlatformInfo(platforms[i], CL_PLATFORM_VENDOR, 0, NULL, &size);
        status = clGetPlatformInfo(platforms[i], CL_PLATFORM_VENDOR, size, str, NULL);
        printf("CL_PLATFORM_VENDOR: %s\n", str);

        status = clGetPlatformInfo(platforms[i], CL_PLATFORM_VERSION, 0, NULL, &size);
        status = clGetPlatformInfo(platforms[i], CL_PLATFORM_VERSION, size, str, NULL);
        printf("CL_PLATFORM_VERSION: %s\n", str);

        status = clGetPlatformInfo(platforms[i], CL_PLATFORM_PROFILE, 0, NULL, &size);
        status = clGetPlatformInfo(platforms[i], CL_PLATFORM_PROFILE, size, str, NULL);
        printf("CL_PLATFORM_PPROFILE: %s\n", str);

        status = clGetPlatformInfo(platforms[i], CL_PLATFORM_EXTENSIONS, 0, NULL, &size);
        status = clGetPlatformInfo(platforms[i], CL_PLATFORM_EXTENSIONS, size, str, NULL);
        printf("CL_PLATFORM_EXTENSIONS: %s\n", str);

        cl_uint numDevices; // 获取 device 数量
        status = clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_DEFAULT, 0, NULL, &numDevices);    
        printf("numDevices: %d\n", numDevices);

        cl_device_id *devices; // 获取 device id
        devices = (cl_device_id *) malloc(numDevices * sizeof(cl_device_id));
        status = clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_DEFAULT, numDevices, devices, NULL);

        for (int j = 0; j < numDevices; j++) {  // 遍历所有 device
            char buffer[10240];
            cl_uint buf_uint;
            cl_ulong buf_ulong;
            printf("Device %d info:\n", j);
            clGetDeviceInfo(devices[j], CL_DEVICE_NAME, sizeof(buffer), buffer, NULL);
            printf("  DEVICE_NAME = %s\n", buffer);
            clGetDeviceInfo(devices[j], CL_DEVICE_VENDOR, sizeof(buffer), buffer, NULL);
            printf("  DEVICE_VENDOR = %s\n", buffer);
            clGetDeviceInfo(devices[j], CL_DEVICE_VENDOR_ID, sizeof(buffer), buffer, NULL);
            printf("  CL_DEVICE_VENDOR_ID = %s\n", buffer);
            clGetDeviceInfo(devices[j], CL_DEVICE_VERSION, sizeof(buffer), buffer, NULL);
            printf("  DEVICE_VERSION = %s\n", buffer);
            clGetDeviceInfo(devices[j], CL_DRIVER_VERSION, sizeof(buffer), buffer, NULL);
            printf("  DRIVER_VERSION = %s\n", buffer);
            clGetDeviceInfo(devices[j], CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(buf_uint), &buf_uint, NULL);
            printf("  DEVICE_MAX_COMPUTE_UNITS = %u\n", (unsigned int) buf_uint);
            clGetDeviceInfo(devices[j], CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(buf_uint), &buf_uint, NULL);
            printf("  DEVICE_MAX_CLOCK_FREQUENCY = %u\n", (unsigned int) buf_uint);
            clGetDeviceInfo(devices[j], CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(buf_ulong), &buf_ulong, NULL);
            printf("  DEVICE_GLOBAL_MEM_SIZE = %llu\n", (unsigned long long) buf_ulong);
        }
    }
    return 0;
}

int createCLEnv(int targetPlatform, cl_device_id &device, 
                cl_context &context, cl_command_queue& commandQueue) {
    cl_int status;
    cl_platform_id *platform = new cl_platform_id;
    status = clGetPlatformIDs(targetPlatform, platform, NULL);   // 使用第一个平台

    cl_context_properties cprops[] = {CL_CONTEXT_PLATFORM, (cl_context_properties) platform[0],     // 生成 context
                                      CL_PRINTF_CALLBACK_ARM, (cl_context_properties) printfCallback,
                                      CL_PRINTF_BUFFERSIZE_ARM, (cl_context_properties) 0x100000, 0};
    context = clCreateContextFromType(cprops, CL_DEVICE_TYPE_GPU, NULL, NULL, &status);

    // 先得到设备列表的长度，注意这里的长度指的是Byte. numDevices = deviceListSize / sizeof(cl_device_id)
    // 然后得到 devices 列表. 这里通过 clGetContextInfo 得到的 devices 和 clGetDeviceIDs 是一样的
    // cl_device_id *devices = (cl_device_id *) malloc(deviceListSize);
    size_t deviceListSize;
    status = clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &deviceListSize);
    std::vector<cl_device_id> devices(deviceListSize / sizeof(cl_device_id));
    status = clGetContextInfo(context, CL_CONTEXT_DEVICES, deviceListSize, devices.data(), NULL);
    commandQueue = clCreateCommandQueue(context, devices[0], CL_QUEUE_PROFILING_ENABLE, &status);
    device = devices[0];
    return status;
}

int compileKernel(const std::string srcCode, const size_t srcSize, const std::string kernelName,
                  const cl_context context, const cl_device_id device, cl_program &program, cl_kernel& kernel) {
    cl_int status;
    const char *srcCode_[] = {srcCode.c_str()};
    const size_t *srcSize_ = {&srcSize};
    // 装载内核程序（注意这个clCreateProgramWithSource的第三个参数是【代码字符串数组】，如["foo", "bar"]，第四个参数也是）
    program = clCreateProgramWithSource(context, 1, srcCode_, srcSize_, &status);
    status = clBuildProgram(program, 1, &device, NULL, NULL, NULL); // 为指定的设备编译 CL program
    CHECK(status, "Build Program Error");
    kernel = clCreateKernel(program, kernelName.c_str(), &status);  // 得到指定名字的内核实例的句柄
    CHECK(status, "Create Kernel Error");
    return status;
}

cl_mem createImage2d(cl_context context, cl_channel_type type, int width, int height, void* data, cl_int *status) {
    cl_image_desc desc;
    desc.image_type = CL_MEM_OBJECT_IMAGE2D;
    desc.image_width = width;
    desc.image_height = height;
    desc.image_depth = 0;
    desc.image_row_pitch = 0;
    desc.image_slice_pitch = 0;
    desc.num_mip_levels = 0;
    desc.num_samples = 0;
    desc.buffer = nullptr;

    cl_image_format format;
    format.image_channel_order = CL_RGBA;
    format.image_channel_data_type = type;

    cl_mem_flags flags = CL_MEM_READ_WRITE;
    if (data != nullptr) {
        flags |= CL_MEM_COPY_HOST_PTR;
    }
    else {
        flags |= CL_MEM_ALLOC_HOST_PTR;
    }

    cl_mem texture = clCreateImage(context, flags, &format, &desc, data, status);
    return texture;
}

double profileTime(cl_event profEvent, bool log) {
    cl_int status;
    cl_ulong evStartTime = static_cast<cl_ulong>(0);
    cl_ulong evEndTime = static_cast<cl_ulong>(0);
    status = clGetEventProfilingInfo(profEvent, CL_PROFILING_COMMAND_START, sizeof(cl_ulong), &evStartTime, NULL);
    status = clGetEventProfilingInfo(profEvent, CL_PROFILING_COMMAND_END, sizeof(cl_ulong), &evEndTime, NULL);
    if (log)
        std::cout << "Run Time: " << static_cast<double>(evEndTime - evStartTime) / 1e6 << "ms" << std::endl;
    return (static_cast<double>(evEndTime - evStartTime) / 1e6);
}

int releaseEnv(cl_context context, cl_command_queue commandQueue) {
    cl_int status;
    status = clReleaseCommandQueue(commandQueue);
    status = clReleaseContext(context);
    return status;
}

}